// Copyright Epic Games, Inc. All Rights Reserved.

#include "print_cmd.h"

#include <zencore/compactbinarypackage.h>
#include <zencore/compactbinaryvalidation.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/string.h>
#include <zenutil/packageformat.h>

using namespace std::literals;

namespace zen {

static void
PrintCbObject(CbObject Object)
{
	ExtendableStringBuilder<1024> ObjStr;
	CompactBinaryToJson(Object, ObjStr);
	ZEN_CONSOLE("{}", ObjStr);
}

static void
PrintCompactBinary(IoBuffer Data)
{
	ExtendableStringBuilder<1024> StreamString;
	CompactBinaryToJson(Data.GetView(), StreamString);
	ZEN_CONSOLE("{}", StreamString);
}

PrintCommand::PrintCommand()
{
	m_Options.add_options()("h,help", "Print help");
	m_Options.add_option("", "s", "source", "Object payload file (use '-' to read from STDIN)", cxxopts::value(m_Filename), "<file name>");
	m_Options.parse_positional({"source"});
}

PrintCommand::~PrintCommand() = default;

int
PrintCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
	ZEN_UNUSED(GlobalOptions);

	if (!ParseOptions(argc, argv))
	{
		return 0;
	}

	// Validate arguments

	if (m_Filename.empty())
		throw std::runtime_error("No file specified");

	FileContents Fc;

	if (m_Filename == "-")
	{
		Fc = ReadStdIn();
	}
	else
	{
		Fc = ReadFile(m_Filename);
	}

	if (Fc.ErrorCode)
	{
		ZEN_ERROR("Failed to read file '{}': {}", m_Filename, Fc.ErrorCode.message());

		return 1;
	}

	IoBuffer Data = Fc.Flatten();

	IoHash	 RawHash;
	uint64_t RawSize;
	if (CompressedBuffer::ValidateCompressedHeader(Data, RawHash, RawSize))
	{
		ZEN_CONSOLE("Compressed binary: size {}, raw size {}, hash: {}", Data.GetSize(), RawSize, RawHash);
	}
	else if (IsPackageMessage(Data))
	{
		CbPackage Package = ParsePackageMessage(Data);

		CbObject					  Object	  = Package.GetObject();
		std::span<const CbAttachment> Attachments = Package.GetAttachments();

		ZEN_CONSOLE("Package - {} attachments, object hash {}", Package.GetAttachments().size(), Package.GetObjectHash());
		ZEN_CONSOLE("");

		int AttachmentIndex = 1;

		for (const CbAttachment& Attachment : Attachments)
		{
			std::string AttachmentSize = "n/a";
			const char* AttachmentType = "unknown";

			if (Attachment.IsCompressedBinary())
			{
				AttachmentType = "Compressed";
				AttachmentSize = fmt::format("{} ({} uncompressed)",
											 Attachment.AsCompressedBinary().GetCompressedSize(),
											 Attachment.AsCompressedBinary().DecodeRawSize());
			}
			else if (Attachment.IsBinary())
			{
				AttachmentType = "Binary";
				AttachmentSize = fmt::format("{}", Attachment.AsBinary().GetSize());
			}
			else if (Attachment.IsObject())
			{
				AttachmentType = "Object";
				AttachmentSize = fmt::format("{}", Attachment.AsObject().GetSize());
			}
			else if (Attachment.IsNull())
			{
				AttachmentType = "null";
			}

			ZEN_CONSOLE("Attachment #{} : {}, {}, size {}", AttachmentIndex, Attachment.GetHash(), AttachmentType, AttachmentSize);

			++AttachmentIndex;
		}

		ZEN_CONSOLE("---8<---");

		PrintCbObject(Object);
	}
	else if (CbValidateError Result = ValidateCompactBinary(Data,
															CbValidateMode::Default | CbValidateMode::Names | CbValidateMode::Format |
																CbValidateMode::Package | CbValidateMode::PackageHash);
			 Result == CbValidateError::None)
	{
		PrintCompactBinary(Data);
	}
	else
	{
		ZEN_ERROR("Data in file '{}' does not appear to be compact binary (validation error {:#x})", m_Filename, uint32_t(Result));

		return 1;
	}

	return 0;
}

//////////////////////////////////////////////////////////////////////////

PrintPackageCommand::PrintPackageCommand()
{
	m_Options.add_options()("h,help", "Print help");
	m_Options.add_option("", "s", "source", "Package payload file", cxxopts::value(m_Filename), "<file name>");
	m_Options.parse_positional({"source"});
}

PrintPackageCommand::~PrintPackageCommand()
{
}

int
PrintPackageCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
	ZEN_UNUSED(GlobalOptions);

	if (!ParseOptions(argc, argv))
	{
		return 0;
	}

	// Validate arguments

	if (m_Filename.empty())
		throw std::runtime_error("No file specified");

	FileContents Fc	  = ReadFile(m_Filename);
	IoBuffer	 Data = Fc.Flatten();
	CbPackage	 Package;

	bool Ok = Package.TryLoad(Data) || legacy::TryLoadCbPackage(Package, Data, &UniqueBuffer::Alloc);

	if (Ok)
	{
		ExtendableStringBuilder<1024> ObjStr;
		CompactBinaryToJson(Package.GetObject(), ObjStr);
		ZEN_CONSOLE("{}", ObjStr);
	}
	else
	{
		ZEN_ERROR("error: malformed package?");
	}

	return 0;
}

}  // namespace zen
